using System;
using MFWebSockets.Core.Utilities.Collections;

namespace MFWebSockets.Core.Utilities.Algorithms
{
    //And Again by Julius R. Friedman for the .Net Micro Framework for a Community Example
    // This code was adapted from Mihai Popescu's post at c-sharpcorder.com
    // http://www.c-sharpcorner.com/UploadFile/mihai.popescu/SHA1Implementation03142007193422PM/SHA1Implementation.aspx
    // http://www.netmf.com/Discussion/Forums/SingleForum/SingleThread.aspx?mode=singleThread&thread=749469d7-55f0-425b-a03c-dca26e235cf7

    /// <summary>
    /// TODO STANDARDIZE IMPLEMENTATION WITH MD5
    /// PROVIDE A WAY TO CHECK FOR CRYPTO LIBRARIES and UTILIZE THEM IF PRESENT
    /// </summary>

    public class SHA1
    {
        internal const int z = 0, Hl = 5, Hl6 = 30, WKl = 80, c = 1, d = 2, e = 3, g = 4, g5 = 20, g10 = 40, g15 = 60, h = 8, g3 = 12, h2 = 16, h3 = 24, h4 = 32, h6 = 48, h7 = 56, h8 = 64, h56 = 448, h64 = 512;

        UInt32[] H = new UInt32[Hl];
        UInt32[] W = new UInt32[WKl];
        UInt32[] K = new UInt32[WKl];

        public SHA1() { initK(); }

        const uint K0 = 0x5A827999;
        const uint K1 = 0x6ED9EBA1;
        const uint K2 = 0x8F1BBCDC;
        const uint K3 = 0xCA62C1D6;

        internal void initK()
        {
            //Build Tables
            for (int t = 0; t < g5; ++t) K[t] = K0;
            for (int t = g5; t < g10; ++t) K[t] = K1;
            for (int t = g10; t < g15; ++t) K[t] = K2;
            for (int t = g15; t < WKl; ++t) K[t] = K3;
        }

        const uint H0 = 0x67452301;
        const uint H1 = 0xEFCDAB89;
        const uint H2 = 0x98BADCFE;
        const uint H3 = 0x10325476;
        const uint H4 = 0xC3D2E1F0;

        internal void initH()
        {
            //Initialize Seed Values
            H[z] = H0;
            H[1] = H1;
            H[2] = H2;
            H[3] = H3;
            H[4] = H4;
        }

        internal UInt32 S(UInt32 X, int n)
        {
            return (X << n) | (X >> (h4 - n));
        }

        internal UInt32 f(UInt32 B, UInt32 C, UInt32 D, int t)
        {
            if (t < g5) return ((B & C) | ((UInt32)(~B) & D));
            else if (t < g10) return B ^ C ^ D;
            else if (t < g15) return (B & C) | (B & D) | (C & D);
            else return B ^ C ^ D;           
        }

        const long Remainder = 4294967296;

        internal void blockHash(Byte[] u8)
        {
            //Do the Hash Algorithmn
            unchecked
            {
                for (int t = z; t < h2; ++t)
                    W[t] = ((uint)u8[g * t] << h3) + ((uint)u8[g * t + 1U] << h2) + ((uint)u8[g * t + 2U] << h) + ((uint)u8[g * t + e] << z);

                for (int t = h2; t < WKl; ++t)
                    W[t] = S(W[t - e] ^ W[t - h] ^ W[t - 14] ^ W[t - h2], c);


                UInt32 A = H[z];
                UInt32 B = H[c];
                UInt32 C = H[d];
                UInt32 D = H[e];
                UInt32 E = H[g];

                for (int t = z; t < WKl; ++t)
                {
                    UInt32 TEMP = (UInt32)((S(A, Hl) + f(B, C, D, t) + E + W[t] + K[t]) % Remainder);
                    E = D;
                    D = C;
                    C = S(B, Hl6);
                    B = A;
                    A = TEMP;
                }

                H[z] = (UInt32)((H[0] + A) % Remainder);
                H[c] = (UInt32)((H[c] + B) % Remainder);
                H[d] = (UInt32)((H[d] + C) % Remainder);
                H[e] = (UInt32)((H[e] + D) % Remainder);
                H[g] = (UInt32)((H[g] + E) % Remainder);
            }
        }

        public string MessageHash(string Message)
        {
            return MessageHash(Tools.ConvertToBitArray(ref Message));
        }

        /// <summary>
        /// Computing the message digest.
        /// </summary>
        /// <param name="message">
        /// Initial message.
        /// </param>
        /// <returns>
        /// Message Digest.
        /// </returns>
        internal String InternalMessageHash(BitArray message)
        {
            // initialize H values
            initH();

            // message padding
            message = MessagePadding(message);

            // computing each 512-bits block
            for (int i = z, E = message.Count; i < E; ++i)
            {
                // copying our needed bits in a 512 sized BitArray
                BitArray blockMessage = new BitArray(h64);
                for (int j = z; j < h64; ++j) blockMessage[j] = message[h64 * i + j];

                // inversing the bitfield
                //As fast as possible
                bool temp;
                for (int j = z; j < h8; ++j) for (int k = z, swapA = h * j + k, swapB = h * (j + c) - k - c; k < g; ++k, swapA = h * j + k, swapB = h * (j + 1) - k - c)
                    {
                        //blockMessage[swapA] ^= blockMessage[swapB];
                        //blockMessage[swapB] ^= blockMessage[swapB];
                        //blockMessage[swapA] ^= blockMessage[swapB];
                        temp = blockMessage[swapA];
                        blockMessage[swapA] = blockMessage[swapB];
                        blockMessage[swapB] = temp;
                    }

                // Coping the bitfield in a byte array
                Byte[] u8 = new Byte[h8];                
                blockMessage.CopyTo(u8, z);

                // hashing the block
                blockHash(u8);
            }

            return string.Concat(Tools.Hex(H[z]), Tools.Hex(H[c]), Tools.Hex(H[d]), Tools.Hex(H[e]), Tools.Hex(H[g]));
        }

        /// <summary>
        /// Computing the message digest.
        /// </summary>
        /// <param name="message">
        /// Initial message.
        /// </param>
        /// <returns>
        /// Message Digest.
        /// </returns>
        public String MessageHash(BitArray message)
        {
            return InternalMessageHash(message);
        }

        internal BitArray MessagePadding(BitArray message)
        {
            // Saving the size of the message in bits
            Int32[] messageL = new Int32[1]; // For message longer then pow(2, 32) - 65 bits Int32[] messageL = new Int32[2]; 
            int messageCount = message.Count;
            messageL[z] = messageCount; // The message count should be saved in two words

            // add a bit set as '1' at the end of the message
            message.Length += c;
            message.Set(messageL[z], true);

            //add a series of bytes set as '0' so our message length is congruent to 448 (mod 512) 
            if (messageCount > h56)
            {
                int mCDiff = messageCount - h56;
                message.Length += mCDiff % h64 == z ? z : h64 - mCDiff % h64;
            }
            else
                message.Length = h56;

            // we add two final words to our message containing the length of the initial message in bits
            BitArray length = new BitArray(messageL);
            message.Length += h8;
            for (int j = z; j < h4; ++j) message[message.Count - j - c] = length[j];
            
            return message;
        }
    }
}
